#!/usr/bin/env python
'''%prog [OPTIONS] [ARGS ...]

Create and populate a directory structure from a tree-like output 
string representation.

By default, all files are simply 0-length files, but this behaviour is 
easily extensisible.

Syntax rules:

-- Indentation can be space or tab-based
-- Names ending with "/" are treated as directories

Input can be read from either stdin or from a file and a moose once bit my sister.

Parsable directory tree input strings can be of the form generated 
by the "tree" command:
    
    `tree -F --noreport DIR`. 

For instance, the two following tree representations are equivalent:

=== simpler style: ===

monty/
    thebrightsideoflife.mp3
    mints/
        mr.creosote
        wafer_thin/
            buckets.bin


=== `tree` style: ===

monty/
|-- thebrightsideoflife.mp3
`-- mints/
    |-- mr.creosote
    `-- wafer_thin/
        `-- buckets.bin

'''
import os, sys, re


DJANGO_APP = '''%(app)s/
    __init__.py
    admin.py
    context_processors.py
    forms.py
    models.py
    tests.py
    urls.py
    views.py
    conf/
        __init__.py
        %(app)s_settings.py
    fixtures/
    templates/
        %(app)s/
    templatetags/
        __init__.py
    management/
        __init__.py
        commands/
            __init__.py
'''



#-------------------------------------------------------------------------------
def _empty_file(filename):
    '''
    Create a 0-length file
    '''
    if not os.path.exists(filename):
        file(filename, 'w').close()


#=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=
class TreeParseError(Exception): 
    pass


#===============================================================================
class Tree(object):

    PARSE_REGEX = re.compile(
        r'''^
            ((?:[|`\ ](?:--|  ) )+)
            (.*)$
        ''',
        re.VERBOSE
    )

    #---------------------------------------------------------------------------
    def __init__(self, parent, dirname, files=None, subdirs=None):
        self.parent = parent
        self.dirname = dirname
        self.files = files or []
        self.subdirs = subdirs or []
        self.items = []

    #---------------------------------------------------------------------------
    def __str__(self):
        return self.dirname
        
    #---------------------------------------------------------------------------
    def add(self, item):
        if item.endswith('/'):
            item = Tree(self, item)
            self.subdirs.append(item)
        else:
            if item[-1] in '@|':
                return
                
            self.files.append(item.rstrip('*='))
            
        self.items.append(item)
        
    #---------------------------------------------------------------------------
    def build(self, file_handler=_empty_file, py_mode=False):
        if not os.path.exists(self.dirname):
            os.mkdir(self.dirname)
            
        os.chdir(self.dirname)
        
        if py_mode:
            file_handler('__init__.py')
            
        for fname in self.files:
            file_handler(fname.rstrip('*'))
            
        for subdir in self.subdirs:
            subdir.build(file_handler, py_mode)
        
        os.chdir('..')

    #---------------------------------------------------------------------------
    def print_tree(self, depth=0, indent='    ', stream=sys.stdout):
        print >> stream, '%s%s' % (indent * depth, self)
        for item in self.items:
            if isinstance(item, Tree):
                item.print_tree(depth + 1, indent, stream)
            else:
                print >> stream,  '%s%s' % (indent * (depth + 1), item)
                
    #---------------------------------------------------------------------------
    @staticmethod
    def parse(text):
        lines = text.strip().splitlines()
        current = root = Tree(None, lines.pop(0))
        line_no = 1
        previous = 1
        for line in lines:
            line_no += 1
            if line.startswith('\t'):
                fname = line.strip()
                depth = len(line) - len(fname)
            else:
                m = Tree.PARSE_REGEX.match(line)
                if not (m and  len(m.groups()) == 2):
                    raise TreeParseError(
                        'Oh shit... invalid format: [%d] %s' % (line_no, line)
                    )
                
                prefix, fname = m.groups()
                depth = len(prefix) / 4
            
            
            if depth > previous:
                current = current.subdirs[-1]
                    
            elif depth < previous:
                while depth < previous:
                    current = current.parent
                    previous -= 1
    
            current.add(fname)
            previous = depth
            
        return root

    #---------------------------------------------------------------------------
    @staticmethod
    def create(tree_string, verbose=True, py_mode=False):
        tree = Tree.parse(tree_string)
        tree.build(py_mode=py_mode)
        if verbose:
            print 'Created tree:'
            tree.print_tree()


#-------------------------------------------------------------------------------
def main(args=None):
    from optparse import OptionParser
    parser = OptionParser(usage=__doc__)
    parser.add_option(
        '-p', 
        '--python', 
        dest='py_mode',
        action='store_true',
        help='Use Python mode (create __init__.py files in all directories)'
    )

    parser.add_option(
        '-v', 
        '--verbose', 
        dest='verbose',
        action='store_true',
        help='Use verbose output'
    )

    parser.add_option(
        '', 
        '--django', 
        dest='django_app',
        action='append',
        help='Create a robust Django app directory structure'
    )
    
    options, args = parser.parse_args()
    if options.django_app:
        for app in options.django_app:
            Tree.create(DJANGO_APP % dict(app=app), options.verbose)
    else:
        args = args or ['-']
        for arg in args:
            stream = sys.stdin if arg == '-' else open(arg)
            Tree.create(stream.read(), options.verbose, options.py_mode)
            if arg == '-':
                stream.close()



################################################################################
if __name__ == '__main__':
    main()
